﻿/**
 * Copyright (C) 2013 original author and authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NGuice.Inject.Internal;
using NGuice.Inject.Internal.Util;
using NGuice.Inject.Matcher;
using AopAlliance.Intercept;
using System.Reflection;
using NGuice.Inject.Binder;

namespace NGuice.Inject.Spi
{
    //complete！
    /// <summary>
    /// 暴露元素的模块，以便可以内省、验证或<code>Element#applyTo(Binder) rewritten</code>
    /// </summary>
    public sealed class Elements
    {
        private readonly GetInstanceBindingTargetVisitor GET_INSTANCE_VISITOR = new GetInstanceBindingTargetVisitor();

        /// <summary>
        /// 记录被模块运行的元素
        /// </summary>
        /// <param name="modules"></param>
        /// <returns></returns>
        public static IList<IElement> GetElements(params IModule[] modules)
        {
            return GetElements(Stage.DEVELOPMENT, modules.ToList());
        }

        public static IList<IElement> GetElements(Stage stage, params IModule[] modules)
        {
            return GetElements(stage, modules.ToList());
        }

        public static IList<IElement> GetElements(IEnumerable<IModule> modules)
        {
            return GetElements(Stage.DEVELOPMENT, modules);
        }

        public static IList<IElement> GetElements(Stage stage, IEnumerable<IModule> modules)
        {
            RecordingBinder binder = new RecordingBinder(stage);
            foreach (IModule module in modules)
            {
                binder.Install(module);
            }
            return new ReadOnlyList<IElement>(binder.elements);
        }
    }

    internal sealed class GetInstanceBindingTargetVisitor : DefaultBindingTargetVisitor<object, object>
    {
        public override object Visit(IInstanceBinding<object> binding)
        {
            return binding.Instance;
        }

        protected override object VisitOther(IBinding binding)
        {
            throw new ArgumentException();
        }

    }

    internal class RecordingBinder : IBinder, IPrivateBinder
    {
        private readonly Stage stage;
        private readonly ISet<IModule> modules;
        internal readonly IList<IElement> elements;
        private readonly object source;
        private readonly SourceProvider sourceProvider;

        /** The binder where exposed bindings will be created */
        private readonly RecordingBinder parent;
        private readonly PrivateElementsImpl privateElements;

        internal RecordingBinder(Stage stage)
        {
            this.stage = stage;
            this.modules = new HashSet<IModule>();
            this.elements = Lists.NewArrayList<IElement>();
            this.source = null;
            this.sourceProvider = SourceProvider.DEFAULT_INSTANCE.PlusSkippedClasses(
                typeof(Elements), typeof(RecordingBinder), typeof(AbstractModule),
                typeof(ConstantBindingBuilderImpl<>), typeof(AbstractBindingBuilder<>), typeof(BindingBuilder<>));
            this.parent = null;
            this.privateElements = null;
        }

        /// <summary>
        /// 创建一个记录binder，其为原型的后台
        /// </summary>
        /// <param name="prototype"></param>
        /// <param name="source"></param>
        /// <param name="sourceProvider"></param>
        internal RecordingBinder(RecordingBinder prototype, object source, SourceProvider sourceProvider)
        {
            Preconditions.CheckArgument(source == null ^ sourceProvider == null);

            this.stage = prototype.stage;
            this.modules = prototype.modules;
            this.elements = prototype.elements;
            this.source = source;
            this.sourceProvider = sourceProvider;
            this.parent = prototype.parent;
            this.privateElements = prototype.privateElements;
        }

        /// <summary>
        /// 创建一个私有的recording binder
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="privateElements"></param>
        internal RecordingBinder(RecordingBinder parent, PrivateElementsImpl privateElements)
        {
            this.stage = parent.stage;
            this.modules = new HashSet<IModule>();
            this.elements = privateElements.GetElementsMutable();
            this.source = parent.source;
            this.sourceProvider = parent.sourceProvider;
            this.parent = parent;
            this.privateElements = privateElements;
        }

        /*if[AOP]*/
        public void BindInterceptor(IMatcher classMatcher, IMatcher methodMatcher, params IMethodInterceptor[] interceptors)
        {
            elements.Add(new InterceptorBinding(Source, classMatcher, methodMatcher, interceptors));
        }
        /*end[AOP]*/

        public void BindScope(Type annotationType, IScope scope)
        {
            elements.Add(new ScopeBinding(Source, annotationType, scope));
        }

        public void BindScope<T>(IScope scope)
           where T : Attribute
        {
            BindScope(typeof(T), scope);
        }

        // it is safe to use the type literal for the raw type
        public void RequestInjectionForNoGeneric(object instance)
        {
            dynamic ins = instance;//C#没有泛型通配符，使用动态变量解决此问题
            RequestInjection(ins);
        }

        public void RequestInjection<T>(T instance)
        {
            elements.Add(new InjectionRequest<T>(Source, instance));
        }

        public IMembersInjector<T> GetMembersInjector<T>()
        {
            return (IMembersInjector<T>)GetMembersInjector(typeof(T));
        }

        public IMembersInjector GetMembersInjector(Type type)
        {
            ConstructorInfo c = typeof(MembersInjectorLookup<>).MakeGenericType(new Type[] { type }).GetConstructor(new Type[] { typeof(object) });
            //TODO:C#泛型没有通配符，如果不实用动态对象，则需要使用反射，这里为了方便，使用动态对象执行！
            dynamic element = (IElement)c.Invoke(new object[] { Source });
            elements.Add(element);
            return element.MembersInjector;
        }

        public void BindListener(IMatcher typeMatcher, ITypeListener listener)
        {
            elements.Add(new TypeListenerBinding(Source, listener, typeMatcher));
        }

        public void RequestStaticInjection(params Type[] types)
        {
            foreach (Type type in types)
            {
                elements.Add(new StaticInjectionRequest(Source, type));
            }
        }

        public void Install(IModule module)
        {
            if (modules.Add(module))
            {
                IBinder binder = this;
                if (module is PrivateModule)
                {
                    binder = binder.NewPrivateBinder();
                }

                try
                {
                    module.Configure(binder);
                }
                catch (Exception e)
                {
                    ICollection<Message> messages = Errors.GetMessagesFromThrowable(e);
                    if (messages.Count != 0)
                    {
                        foreach (var m in messages)
                        {
                            elements.Add(m);
                        }
                    }
                    else
                    {
                        AddError(e);
                    }
                }
                binder.Install(ProviderMethodsModule.ForModule(module));
            }
        }

        public Stage CurrentStage
        {
            get
            {
                return stage;
            }
        }

        public void AddError(string message, params object[] arguments)
        {
            elements.Add(new Message(Source, Errors.Format(message, arguments)));
        }

        public void AddError(Exception t)
        {
            string message = "An exception was caught and reported. Message: " + t.Message;
            elements.Add(new Message(ImmutableList<object>.Of(Source), message, t));
        }

        public void AddError(Message message)
        {
            elements.Add(message);
        }

        public IAnnotatedBindingBuilder<T> Bind<T>(Key<T> key)
        {
            return new BindingBuilder<T>(this, elements, Source, key);
        }

        public IAnnotatedBindingBuilder<T> Bind<T>()
        {
            return Bind(Key<T>.Get());
        }

        ILinkedBindingBuilder<T> IBinder.Bind<T>(Key<T> key)
        {
            return Bind<T>(key);
        }

        public IAnnotatedConstantBindingBuilder BindConstant()
        {
            return new ConstantBindingBuilderImpl<AsyncVoid>(this, elements, Source);
        }

        public IProvider<T> GetProvider<T>(Key<T> key)
        {
            ProviderLookup<T> element = new ProviderLookup<T>(Source, key);
            elements.Add(element);
            return element.Provider;
        }

        public IProvider<T> GetProvider<T>()
        {
            return GetProvider(Key<T>.Get());
        }

        public IProvider GetProvider(Key key)
        {
            //TODO:反射调用泛型方法
            foreach (MethodInfo method in GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance))
            {
                if (method.Name == "GetProvider" && method.IsGenericMethod
                    && method.GetParameters().Length == 1 
                    && method.GetParameters()[0].ParameterType.IsGenericType)
                {
                    return (IProvider)method.MakeGenericMethod(key.GetType().GetGenericArguments()[0]).Invoke(this, new object[] { key });
                }
            }
            throw new NotImplementedException("not implement generic GetProvider(key<>) method");
        }

        public void ConvertToTypes(IMatcher typeMatcher, ITypeConverter converter)
        {
            elements.Add(new TypeConverterBinding(Source, typeMatcher, converter));
        }

        public RecordingBinder WithSource(object source)
        {
            return new RecordingBinder(this, source, null);
        }

        IBinder IBinder.WithSource(object source)
        {
            return WithSource(source);
        }

        IPrivateBinder IPrivateBinder.WithSource(object source)
        {
            return WithSource(source);
        }

        public RecordingBinder SkipSources(params Type[] classesToSkip)
        {
            // if a source is specified explicitly, we don't need to skip sources
            if (source != null)
            {
                return this;
            }

            SourceProvider newSourceProvider = sourceProvider.PlusSkippedClasses(classesToSkip);
            return new RecordingBinder(this, null, newSourceProvider);
        }

        IBinder IBinder.SkipSources(params Type[] classesToSkip)
        {
            return SkipSources(classesToSkip);
        }

        IPrivateBinder IPrivateBinder.SkipSources(params Type[] classesToSkip)
        {
            return SkipSources(classesToSkip);
        }

        public IPrivateBinder NewPrivateBinder()
        {
            PrivateElementsImpl privateElements = new PrivateElementsImpl(Source);
            elements.Add(privateElements);
            return new RecordingBinder(this, privateElements);
        }

        public void DisableCircularProxies()
        {
            elements.Add(new DisableCircularProxiesOption(Source));
        }

        public void RequireExplicitBindings()
        {
            elements.Add(new RequireExplicitBindingsOption(Source));
        }

        public void Expose(Key key)
        {
            ExposeInternal(key);
        }

        public IAnnotatedElementBuilder Expose(Type type)
        {
            return ExposeInternal(Key<object>.GetForType(type));
        }

        private class EmptyAnnotatedElementBuilder : IAnnotatedElementBuilder
        {
            public void AnnotatedWith(Type annotationType) { }

            public void AnnotatedWith<T>()
                where T : Attribute { }

            public void AnnotatedWith(Attribute annotation) { }
        }

        private IAnnotatedElementBuilder ExposeInternal(Key key)
        {
            if (privateElements == null)
            {
                AddError("Cannot expose %s on a standard binder. "
                    + "Exposed bindings are only applicable to private binders.", key);
                return new EmptyAnnotatedElementBuilder();
            }

            ExposureBuilder builder = typeof(ExposureBuilder<>).MakeGenericType(key.GetType().GetGenericArguments())
                      .GetConstructor(new Type[] { typeof(IBinder), typeof(object), key.GetType() })
                      .Invoke(new object[] { this, Source, key }) as ExposureBuilder;
            privateElements.AddExposureBuilder(builder);
            return builder;
        }

        protected object Source
        {
            get
            {
                return sourceProvider != null
                    ? sourceProvider.Get()
                    : source;
            }
        }

        public override string ToString()
        {
            return "Binder";
        }

    }
}
